本系列已集結成書從 0 到 Webpack:學習 Modern Web 專案的建置方式,這是一本完整介紹 Webpack 的專書,如有學習 Webpack 相關的問題可以參考此書。
本文講解 webpack 配置項
output
的使用方式。
本文的範例程式放在 peterhpchen/webpack-quest 中,每個程式碼區塊的第一行都會標注檔案的位置,請搭配文章作參考。
output
屬性是設定建置完成的 bundle 要放在哪個目錄以及如何命名。
output
的預設值如下:
// ./demos/output-default/webpack.config.js
const path = require("path");
module.exports = {
output: {
path: path.join(process.cwd(), "dist"),
filename: "[name].js",
},
};
path
定義了 bundle 輸出的路徑,預設是在執行建置工作目錄下的 dist
目錄中。filename
定義生成的檔名,預設值使用了 template string [name]
,這樣可以依照 Chunk 名稱生成對應的檔案。由於
path
設定複雜,每個屬性都有各自的預設值,本文只說明了path
及filename
兩個常用的屬性預設值,其他的預設值可以參考 webpack 代碼。
output
需要使用者配置一個物件,該物件的屬性像是前面說到的 path
及 filename
,每個都有自己的功能,使用者可以依照自己的需求做配置。
接著會說明各屬性的用途及使用方式。
由於
output
的屬性繁多,為避免讀者混淆,本文只會講解幾個常用的屬性,其他的屬性依照情境會在之後的章節介紹。
path
path
屬性設定輸出的目錄,這屬性需要配置絕對路徑(absolute path)。
// ./demos/output-path/webpack.config.demo.js
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "build"),
},
};
使用 Node.js 內建的 path
模組及 __dirname
變數可以解決路徑問題並組成絕對路徑。
output
的 path
屬性可以用 CLI 設定:
// ./demos/output-path/package.json
{
...
"scripts": {
"build:argv": "webpack --output-path $PWD/build",
"build:argv2": "webpack --output-path build"
...
},
...
}
--output-path
可以設定輸出路徑,雖然可以使用相對路徑做設定,但避免問題還是請使用絕對路徑做設置。
$PWD
是存有目前工作目錄的變數,可以用它組出絕對路徑。
path
中可以使用 [hash]
用 Compilation 的 hash 值設定目錄:
// ./demos/output-path/webpack.config.demo.js
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "[hash]"),
},
};
上面的例子會產生下面的結果:
root
|- 8f2404a0220882490931
|- ...
它會產生一個 Compilation 的 hash 值為名稱的目錄。
filename
filename
設定 bundle 輸出的檔案名稱,它有兩種設定方式:
下面我們依序介紹字串值及函式的設定方式。
函式是個擁有 chunkData
的方法,其回傳值必須是合法的 filename
字串值。
filename
字串值可以用一般靜態的名稱做設定,也可以用上節提到的 template string 設定不同的名稱來配置輸出。
下面是個使用字串值設定的範例:
// ./demos/output-filename/webpack.config.demo.js
const path = require("path");
module.exports = {
output: {
filename: "bundle.js",
},
};
執行結果如下:
產生的檔名從原本預設的 Chunk 名稱變為配置檔中的 bundle.js
。
CLI 也可以配置 filename
:
// ./demos/output-filename/package.json
{
...
"scripts": {
"build:argv": "webpack --output-filename bundle.js"
},
...
}
使用 --output-filename
設定 filename
參數。
filename
除了設定檔名外,它還可以是一個相對路徑,以此來建置輸出的目錄結構:
// ./demos/output-filename-path/webpack.config.demo.js
module.exports = {
output: {
filename: "js/bundle.js",
},
};
執行結果為:
root
|- dist
|- /js
|- bundle.js
可以看到在 dist
目錄下多了 js
資料夾,裡面存放 bundle.js
。
output
配置縮寫由於 output.path
及 output.filename
常常會一起做配置,因此 CLI 提供合併兩個設定的參數 --output
:
{
...
"scripts": {
"build": "webpack -output ./build/bundle.js",
"build:short": "webpack -o ./build/bundle.js"
},
...
}
直接將輸出的目錄及檔名作為參數傳入即可, --output
這個選項可以簡寫為 -o
。
filename
的方式配置多個 entry
的時候,會產生多個 Chunk ,也意味著會有多個檔案的輸出。
這時如果還是使用靜態的輸出配置,會造成錯誤:
為避免這個問題,可以使用 template string 做設定, webpack 會依照 Chunk 的狀態轉換 template string 變為相符的名稱:
// ./demos/output-filename-multi/webpack.config.demo.js
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename: "bundle.[name].js",
},
};
執行結果如下:
[name]
是之前有提到的 template string ,它會被替換為對應的 Chunk 名稱。
利用 template string 可以讓每個 bundle 生成不同名稱的檔案,以解決多 bundle 的問題。
filename
// ./demos/output-filename-func/webpack.config.demo.js
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename(chunkData) {
return chunkData.chunk.name === "main"
? "main.js"
: `bundle.${chunkData.chunk.name}.js`;
},
},
};
執行結果如下:
當 output.filename
設定為函式時,這個函式會被每個 Chunk 叫用,並且傳入一個 chunkData
的參數,這個參數擁有此 Chunk 的資訊,我們可以使用 chunkData
判斷改怎麼產生輸出。
Template String 可以將 Chunk 的資料帶入字串值中以產生不同名稱的輸出。
Template String 是藉由 Webpack 內建的 TemplatePathPlugin 驅動的功能,它擁有下面的 template :
[name]
: Chunk 名稱[id]
: Chunk ID[hash]
: Compilation 的 hash 值,只要建置有改變, hash 值就會變化[contenthash]
: 每個 bundle 的 hash 值,只有 bundle 改變時才會變化[chunkhash]
: 每個 chunk 的 hash 值,只有在 chunk 改變時才會變化接下來會用例子說明各個 template 的定義。
[name]
在上面有多個例子使用了 [name]
template ,它表示的是對應的 Chunk 名稱:
// ./demos/output-filename-template/webpack.config.name.js
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename: "[name].js",
},
};
執行結果:
結果下方的 Entrypoint 後面的是 Chunk 名稱,可以看到我們建立的 bundle 依照了 Chunk 名稱的方式被建立出來。
[id]
[id]
會對應每個 Chunk 的 ID :
// ./demos/output-filename-template/webpack.config.id.js
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename: "[id].js",
},
};
執行結果如下:
可以看到 Chunk ID 對應輸出成 bundle 的檔名。
[hash]
, [chunkhash]
, [chunkhash]
[hash]
, [chunkhash]
, [chunkhash]
都是輸出 hash 值,但每個都有不同的構成機制,依照情況有些會變動有些可以保持原本的值,大致可以分為下面這樣:
Actions | [hash] |
[chunkhash] |
[contenthash] |
---|---|---|---|
modify webpack config | change | no change | no change |
modify program file | change | only bundle which file change | only bundle which file change |
modify extract file | change | bundle and extract file change | only extract file change |
[hash]
[hash]
比較好理解,在整個建置過程中唯一的一隻 hash 值,因此不管是配置還是程式變化,它的值就會變化。
由於 [hash]
是唯一的,因此如果有多個 bundle 時,它們會唯一的檔名而合併。
[hash]
的例子可以看 ./demos/output-filename-template/webpack.config.hash.js
,可以試著改變配置檔或是程式觀察變化。
[chunkhash]
[chunkhash]
是每個 Chunk 的 hash 值,因此當一個 Chunk 中的 module 發生變化時, [chunkhash]
會變化,但對於其他的 chunk 來說因為沒有發生變化,所以 hash 值還是原來的樣子。
[chunkhas]
可以參考 ./demos/output-filename-template/webpack.config.chunkhash.js
,嘗試變化程式觀察輸出的檔名。
[contenthash]
[contenthash]
的值是由輸出的 bundle 內容所產生的,我們可以看下面這個例子:
// ./demos/output-filename-hash/src/index.js
import "./style/style.css";
console.log("index");
// ./demos/output-filename-hash/src/index2.js
import "./style/style.css";
console.log("index2");
/* ./demos/output-filename-hash/src/style/style.css */
.hello-world {
color: green;
}
然後配置檔如下:
// ./demos/output-filename-hash/webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename: "[contenthash].js",
},
plugins: [
new MiniCssExtractPlugin({
filename: "[contenthash].css",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
};
這裡使用 MiniCssExtractPlugin
將引入的 .css
拉出來另外產生 .css
檔案。
建置結果如下:
這建置產生了兩個 Chunk : main
與 main2
,但是 bundle 卻有三個,這是因為本來被 index.js
及 index2.js
所引用的 style.css
被 MiniCssExtractPlugin
拉出建立為獨立的檔案。
這時如果我們還是使用 [chunkhash]
的話,只要 .css
或是 .js
其中一方修改,兩個檔案的 hash 都會變化。
webpack 提供了有這個多樣的 hash 值供使用者選用,其最大的目的就是為了讓瀏覽器快取,增加應用程式的效能,這部分在後面的章節會講解到。
有時會需要真的輸出 [id]
這樣的字串當作檔名,可以使用 \
包住中間的值(例如 id
, name
),就可以直接當作字串輸出而不會轉換:
// ./demos/output-filename-template/webpack.config.escape.js
module.exports = {
entry: {
main: "./src/index.js",
main2: "./src/index2.js",
},
output: {
filename: "[name].[\\id\\].js",
},
};
在字串中記得再加一個 \
讓 \
可以傳入。
執行結果如下:
publicPath
publicPath
是處理在部署時候,建置檔案在伺服器中的路徑,以此路徑設定 Chunk 在載入時所需要請求的位置。
我們直接來看個例子:
// ./demos/output-publicpath/webpack.config.js
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "build/js"),
},
};
產生出來的目錄如下:
root
|- build
|- /js
|- 1.js
|- main.js
|- index.html
index.html
中使用 ./js/main.js
引入 main.js
1.js
會在 main.js
中被延遲載入將 build
目錄傳至伺服器上看結果:
這時在載入 1.js
時發生了 404 的錯誤,這是因為載入 1.js
的 index.html
目錄在 build
,而 1.js
是在 build/js
, 因此對於 index.html
來說, 1.js
的路徑應該要是 ./js/1.js
才能正確的載入。
這種情況下就必須要使用 publicPath
:
// ./demos/output-publicpath/webpack.config.js
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "build/js"),
publicPath: "./js/",
},
};
這樣的設定使得 index.html
知道要在 ./js/
中尋找 1.js
資源。
chunkFilename
之前介紹的 filename
是設定 entry
所產生出來的 bundle ,這類的 Chunk 叫做 initial
,而像是上面例子的 1.js
這種延遲載入的 Chunk 叫做 non-initial
,這類 non-initial
的輸出檔名就交給 chunkFilename
做設定。
chunkFilename
的設定方式chunkFilename
的預設值為 [id].js
:
可以看到建置結果檔名為 1.js
。
看到這邊大家應該發現了, chunkFilename
一樣可以使用 template string 做設置。
output
擁有許多複雜的配置,本文講了四個比較常用的屬性:path
, filename
, publicPath
以及 chunkFilename
。
這中間也介紹了一個重要的設定方式: Template String 。在 webpack
中配置所有的輸出檔案時,不管是用 plugin 建立的或是 loader 創建的還是一般 chunk 輸出的檔案,所有的檔名幾乎都可以使用 Template String 來設定,使用正確的 Template 可以大大地增加快取的效能,加快應用程式的運行。